package server;

import entities.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.Timer;

/**
 *
 * @author hxl851
 */
public class GameLogic {

    //Delay of timer
    public static final int DELAY = 40;
    //Frequency of timer
    public static final int RATE = 1000 / DELAY;
    //Money each player has in the beginning of game
    private static final int MONEY = 1000;
    //Total time of the game (unit: second)
    private static final int TOTAL_TIME = 1200;
    //Game status infomation (stored in playerID in first property)
    public static final int COMMON_INFO = 0;
    public static final int PLAYER1 = 1;
    public static final int PLAYER2 = 2;
    public static final int WINNER1 = 3;
    public static final int WINNER2 = 4;
    public static final int DRAW = 5;
    private Socket clientSocket1, clientSocket2;
    private ObjectOutputStream outObj1, outObj2;
    private ObjectInputStream inObj1, inObj2;
    private Timer gameTimer;
    private Thread recievingThread1, recievingThread2;
    private int remainingTime;
    private int counter;
    private int money1, money2;
    private Property gameStatus;
    private PowerStation powerStation1, powerStation2;
    private ArrayList<Asteroid> asteroids;//list for store the asteroids
    private ArrayList<Miner> miners1, miners2;//list for store miner
    private ArrayList<Laser> lasers1, lasers2;//list for store laser
    private ArrayList<Attack> attacks1, attacks2;//list for store attacker

    public GameLogic(Socket clientSocket1, Socket clientSocket2) {
        this.clientSocket1 = clientSocket1;
        this.clientSocket2 = clientSocket2;
        statusInit();
        mapInit();
    }

    //Initiate players and time
    private void statusInit() {
        remainingTime = TOTAL_TIME;// setup the remaining time to be the total time
        counter = 0;
        money1 = MONEY;
        money2 = MONEY;
        gameStatus = new Property(COMMON_INFO, remainingTime, money1, money2, 0, true);
    }

    //Initiate the map
    private void mapInit() {
        //try to draw the bg   bg = new Background(10, 10);
        //Two power stations
        powerStation1 = new PowerStation(PLAYER1, 150, 230, true);
        powerStation2 = new PowerStation(PLAYER2, 830, 230, true);

        //Six asteriods
        asteroids = new ArrayList<Asteroid>();
        //Use different images for these asteriods
        Random r = new Random();
        int type;
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 70, 230, true));
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 150, 150, true));
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 150, 310, true));
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 910, 230, true));
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 830, 150, true));
        type = r.nextInt(4) + 5;
        asteroids.add(new Asteroid(type, 830, 310, true));
        //Initiate other game objects
        miners1 = new ArrayList<Miner>();
        miners2 = new ArrayList<Miner>();
        lasers1 = new ArrayList<Laser>();
        lasers2 = new ArrayList<Laser>();
        attacks1 = new ArrayList<Attack>();
        attacks2 = new ArrayList<Attack>();
    }

    public void gameBegin() throws IOException {
        outObj1 = new ObjectOutputStream(clientSocket1.getOutputStream());
        outObj2 = new ObjectOutputStream(clientSocket2.getOutputStream());
        outObj1.writeObject(new String("start"));
        outObj2.writeObject(new String("start"));
        gameTimer = new Timer(DELAY, new gameStep());
        gameTimer.start();
        inObj1 = new ObjectInputStream(clientSocket1.getInputStream());
        inObj2 = new ObjectInputStream(clientSocket2.getInputStream());
        recievingThread1 = new Thread(new recieving(inObj1, 1));
        recievingThread2 = new Thread(new recieving(inObj2, 2));
        recievingThread1.start();
        recievingThread2.start();
    }

//Timer calls this listener to do logic calculation and keep sending data from server
    public class gameStep implements ActionListener {

        int pc = 0;

        public void actionPerformed(ActionEvent e) {
            if (isGameOver()) {
                send();
                gameTimer.stop();
                return;
            }
            timeCalculation();
            gameProcess();
            send();
        }

        boolean isGameOver() {
            if (!powerStation1.isAlive()) {
                //Power station of player 1 dies, then player 2 wins
                gameStatus.setPlayerID(WINNER2);
                return true;
            }
            if (!powerStation2.isAlive()) {
                //Player 1 wins
                gameStatus.setPlayerID(WINNER1);
                return true;
            }
            if (remainingTime <= 0) {
                //No one wins
                gameStatus.setPlayerID(DRAW);
                return true;
            }
            return false;
        }

        void timeCalculation() {
            counter++;
            if (counter == RATE) {
                counter = 0;
                //One second
                remainingTime--;
                gameStatus.setType(remainingTime);
                if (remainingTime == 0) {
                    gameTimer.stop();
                }
            }
        }

        void gameProcess() {
            pc++;
            if (pc == RATE / 2) {
                pc = 0;
                //Miner works two times in one second
                for (int i = 0; i < miners1.size(); i++) {
                    for (int j = 0; j < asteroids.size(); j++) {
                        if (miners1.get(i).isInRange(asteroids.get(j).getBound())) {
                            money1++;
                            gameStatus.setX(money1);
                            asteroids.get(j).decHealth();
                        }
                    }
                }
                for (int i = 0; i < miners2.size(); i++) {
                    for (int j = 0; j < asteroids.size(); j++) {
                        if (miners2.get(i).isInRange(asteroids.get(j).getBound())) {
                            money2++;
                            gameStatus.setY(money2);
                            asteroids.get(j).decHealth();
                        }
                    }
                }
            }

            //===============================================================
            //Attack part
            //===============================================================
            //Attacks move and attack
            for (int i = 0; i < attacks1.size(); i++) {
                if (attacks1.get(i).isAlive()) {
                    if (attacks1.get(i).move()) {
                        //The attack arrives at its destination
                        int x = attacks1.get(i).getDestX();
                        int y = attacks1.get(i).getDestY();
                        //Its attacking object is power station
                        if (powerStation2.contains(x, y)) {
                            powerStation2.decHealth(Attack.DAMAGE);
                            continue;
                        }
                        //Its attacking object is laser
                        for (int j = 0; j < lasers2.size(); j++) {
                            if (lasers2.get(j).contains(x, y)) {
                                lasers2.get(j).decHealth(Attack.DAMAGE);
                                break;
                            }
                        }
                        //Its attacking object is miner
                        for (int j = 0; j < miners2.size(); j++) {
                            if (miners2.get(j).contains(x, y)) {
                                miners2.get(j).decHealth(Attack.DAMAGE);
                                break;
                            }
                        }
                    }
                }
            }
            for (int i = 0; i < attacks2.size(); i++) {
                if (attacks2.get(i).isAlive()) {
                    if (attacks2.get(i).move()) {
                        //The attack arrives at its destination
                        int x = attacks2.get(i).getDestX();
                        int y = attacks2.get(i).getDestY();
                        //Its attacking object is power station
                        if (powerStation1.contains(x, y)) {
                            powerStation1.decHealth(Attack.DAMAGE);
                            continue;
                        }
                        //Its attacking object is laser
                        for (int j = 0; j < lasers1.size(); j++) {
                            if (lasers1.get(j).contains(x, y)) {
                                lasers1.get(j).decHealth(Attack.DAMAGE);
                                break;
                            }
                        }
                        //Its attacking object is miner
                        for (int j = 0; j < miners1.size(); j++) {
                            if (miners1.get(j).contains(x, y)) {
                                miners1.get(j).decHealth(Attack.DAMAGE);
                                break;
                            }
                        }
                    }
                }
            }
            //Lasers attack
            for (int i = 0; i < lasers1.size(); i++) {
                for (int j = 0; j < attacks2.size(); j++) {
                    if (lasers1.get(i).isInRange(attacks2.get(j).getBound())) {
                        attacks2.get(j).decHealth();
                    }
                }
            }
            for (int i = 0; i < lasers2.size(); i++) {
                for (int j = 0; j < attacks1.size(); j++) {
                    if (lasers2.get(i).isInRange(attacks1.get(j).getBound())) {
                        attacks1.get(j).decHealth();
                    }
                }
            }
        }

        void send() {
            try {
                ArrayList<Property> properties = new ArrayList<Property>();
                //Add the game status information
                properties.add(new Property(gameStatus));
                //Add two power stations
                properties.add(powerStation1.getProperty());//returns playerID, type, x, y, currentHealth, alive
                properties.add(powerStation2.getProperty());
                //Add six asteroids
                for (int i = 0; i < asteroids.size(); i++) {
                    properties.add(asteroids.get(i).getProperty());
                }
                //Add miners
                for (int i = 0; i < miners1.size(); i++) {
                    properties.add(miners1.get(i).getProperty());
                }
                for (int i = 0; i < miners2.size(); i++) {
                    properties.add(miners2.get(i).getProperty());
                }
                //Add lasers
                for (int i = 0; i < lasers1.size(); i++) {
                    properties.add(lasers1.get(i).getProperty());
                }
                for (int i = 0; i < lasers2.size(); i++) {
                    properties.add(lasers2.get(i).getProperty());
                }
                //Add attacks
                for (int i = 0; i < attacks1.size(); i++) {
                    properties.add(attacks1.get(i).getProperty());
                }
                for (int i = 0; i < attacks2.size(); i++) {
                    properties.add(attacks2.get(i).getProperty());
                }
                outObj1.writeObject(properties);
                outObj2.writeObject(properties);
            } catch (IOException ex) {
                Logger.getLogger(GameLogic.class.getName()).log(Level.SEVERE, null, ex);
//                JOptionPane.showMessageDialog(null, ex.getMessage());
//                System.exit(1);
            }
        }
    }

//Add a new GameObject to the list
    private void addObject(int type, String location, int id) {
        String[] loc = location.split(",");
        int x = Integer.valueOf(loc[0]);
        int y = Integer.valueOf(loc[1]);
        //The object should be in players' area
        if (id == 1) {
            if (type == GameObject.ATTACK) {
                if (money1 >= Attack.COST) {
                    Attack a = new Attack(PLAYER1, 190, 240, true);
                    a.setDestination(x, y);
                    attacks1.add(a);
                    money1 -= Attack.COST;
                    gameStatus.setX(money1);
                    gameStatus.setMsg1(null);
                } else {
                    gameStatus.setMsg1("Not enough money");
                }
                return;
            }
            if ((x > 50 && x < 450) && (y > 50 && y < 450)) {
                x = x - x % 20 + 10;//set the position in the centre of the grid
                y = y - y % 20 + 10;
                switch (type) {
                    case GameObject.MINER:
                        if (!canCreate(x, y)) {
                            gameStatus.setMsg1("The position is occupied.");
                            return;
                        }
                        if (money1 >= Miner.COST) {
                            miners1.add(new Miner(PLAYER1, x, y, true));
                            money1 -= Miner.COST;
                            gameStatus.setX(money1);
                            gameStatus.setMsg1(null);
                        } else {
                            gameStatus.setMsg1("Not enough money");
                        }
                        break;
                    case GameObject.LASER:
                        if (!canCreate(x, y)) {
                            gameStatus.setMsg1("The position is occupied.");
                            return;
                        }
                        if (money1 >= Laser.COST) {
                            lasers1.add(new Laser(PLAYER1, x, y, true));
                            money1 -= Laser.COST;
                            gameStatus.setX(money1);
                            gameStatus.setMsg1(null);
                        } else {
                            gameStatus.setMsg1("Not enough money");
                        }
                        break;
                }
            } else {
                gameStatus.setMsg1("Cannot create object outside your camp.");
            }
        } else {
            if (type == GameObject.ATTACK) {
                if (money2 >= Attack.COST) {
                    Attack a = new Attack(PLAYER2, 810, 240, true);// the position where the attacker is coming out.
                    a.setDestination(Integer.valueOf(loc[0]), Integer.valueOf(loc[1]));
                    attacks2.add(a);
                    money2 -= Attack.COST;
                    gameStatus.setY(money2);
                    gameStatus.setMsg2(null);
                } else {
                    gameStatus.setMsg1("Not enough money");
                }
                return;
            }
            if ((x > 550 && x < 950) && (y > 50 && y < 450)) {
                x = x - x % 20 + 10;
                y = y - y % 20 + 10;
                switch (type) {
                    case GameObject.MINER:
                        if (!canCreate(x, y)) {
                            gameStatus.setMsg2("The position is occupied.");
                            return;
                        }
                        if (money2 >= Miner.COST) {
                            miners2.add(new Miner(PLAYER2, x, y, true));
                            money2 -= Miner.COST;
                            gameStatus.setY(money2);
                            gameStatus.setMsg2(null);
                        } else {
                            gameStatus.setMsg2("Not enough money");
                        }
                        break;
                    case GameObject.LASER:
                        if (!canCreate(x, y)) {
                            gameStatus.setMsg2("The position is occupied.");
                            return;
                        }
                        if (money2 >= Laser.COST) {
                            lasers2.add(new Laser(PLAYER2, x, y, true));
                            money2 -= Laser.COST;
                            gameStatus.setY(money2);
                            gameStatus.setMsg2(null);
                        } else {
                            gameStatus.setMsg2("Not enough money");
                        }
                        break;
                }
            } else {
                gameStatus.setMsg2("Cannot create object outside your camp.");
            }
        }
    }

    private boolean canCreate(int x, int y) {
        if (powerStation1.contains(x, y)) {
            return false;
        }
        if (powerStation2.contains(x, y)) {
            return false;
        }
        for (int i = 0; i < asteroids.size(); i++) {
            if (asteroids.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < miners1.size(); i++) {
            if (miners1.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < miners2.size(); i++) {
            if (miners2.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < lasers1.size(); i++) {
            if (lasers1.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < lasers2.size(); i++) {
            if (lasers2.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < attacks1.size(); i++) {
            if (attacks1.get(i).contains(x, y)) {
                return false;
            }
        }
        for (int i = 0; i < attacks2.size(); i++) {
            if (attacks2.get(i).contains(x, y)) {
                return false;
            }
        }
        return true;
    }

    //Thread which is used to recieve data from each player
    class recieving implements Runnable {

        ObjectInputStream inObj;
        int id;

        public recieving(ObjectInputStream inObj, int id) {
            this.inObj = inObj;
            this.id = id;
        }

        public void run() {
            boolean gameOver = false;
            while (!gameOver) {
                try {
                    Object obj = inObj.readObject();
                    if (obj instanceof String) {
                        String msg = (String) obj;
                        if (msg.equals("laser")) {
                            //Player clicks "laser" button
                            if (id == 1) {
                                gameStatus.setMsg1("Click a position to create a laser");
                            } else {
                                gameStatus.setMsg2("Click a position to create a laser");
                            }
                            Object newObj = inObj.readObject();
                            if (newObj instanceof String) {
                                String newMsg = (String) newObj;
                                if (!newMsg.equals("cancel")) {
                                    //Player clicks on WorldPanel
                                    //Create a laser
                                    addObject(GameObject.LASER, newMsg, id);
                                } else {
                                    if (id == 1) {
                                        gameStatus.setMsg1(null);
                                    } else {
                                        gameStatus.setMsg2(null);
                                    }
                                }
                            }
                        } else if (msg.equals("miner")) {
                            //Player clicks "miner" button
                            if (id == 1) {
                                gameStatus.setMsg1("Click a position to create a miner");
                            } else {
                                gameStatus.setMsg2("Click a position to create a miner");
                            }
                            Object newObj = inObj.readObject();
                            if (newObj instanceof String) {
                                String newMsg = (String) newObj;
                                if (!newMsg.equals("cancel")) {
                                    //Player clicks on WorldPanel
                                    //Create a miner
                                    addObject(GameObject.MINER, newMsg, id);
                                } else {
                                    if (id == 1) {
                                        gameStatus.setMsg1(null);
                                    } else {
                                        gameStatus.setMsg2(null);
                                    }
                                }
                            }
                        } else if (msg.equals("attack")) {
                            //Player clicks "attack" button
                            if (id == 1) {
                                gameStatus.setMsg1("Click a position to attack");
                            } else {
                                gameStatus.setMsg2("Click a position to attack");
                            }
                            Object newObj = inObj.readObject();
                            if (newObj instanceof String) {
                                String newMsg = (String) newObj;
                                if (!newMsg.equals("cancel")) {
                                    //Player clicks on WorldPanel
                                    //Create a attack
                                    addObject(GameObject.ATTACK, newMsg, id);
                                } else {
                                    if (id == 1) {
                                        gameStatus.setMsg1(null);
                                    } else {
                                        gameStatus.setMsg2(null);
                                    }
                                }
                            }
                        }
                    }
                } catch (Exception ex) {
                    Logger.getLogger(GameLogic.class.getName()).log(Level.SEVERE, null, ex);
                    JOptionPane.showMessageDialog(null, "Something wrong with Network.");
                    System.exit(1);
                }
            }
        }
    }
}
